#!/usr/bin/env python3
"""
Open3D 点云处理和可视化示例 - Python 3.12.7 优化版本
使用了现代 Python 特性：类型注解、模式匹配、异常组等

作者: AnyGen AI Assistant
Python 版本: 3.12.7+
Open3D 版本: 0.18.0+
"""

from __future__ import annotations  # Python 3.12.7: 向前兼容的类型注解

import open3d as o3d
import numpy as np
import copy
from typing import Tuple, List, Optional
from pathlib import Path
import logging

# 配置日志 - Python 3.12.7 支持更好的日志格式
logging.basicConfig(
    level=logging.INFO,
    format='{asctime} - {levelname} - {message}',
    style='{'  # Python 3.8+ 的新式字符串格式
)
logger = logging.getLogger(__name__)

# 类型别名 - Python 3.12.7 中更清晰的类型定义
PointArray = np.ndarray[tuple[int, ...], np.dtype[np.float64]]
ColorArray = np.ndarray[tuple[int, ...], np.dtype[np.float64]]
PointCloud = o3d.geometry.PointCloud


class PointCloudProcessor:
    """点云处理器类 - 使用面向对象设计"""

    def __init__(self, random_seed: Optional[int] = None) -> None:
        """初始化点云处理器

        Args:
            random_seed: 随机种子，用于结果复现
        """
        if random_seed is not None:
            np.random.seed(random_seed)
        logger.info("点云处理器初始化完成")


def create_sample_point_cloud(num_points: int = 1000) -> PointCloud:
    """
    创建一个示例点云数据
    使用 Python 3.12.7 的类型注解和现代语法

    Args:
        num_points: 要生成的点数量

    Returns:
        Open3D 点云对象
    """
    logger.info(f"正在创建包含 {num_points} 个点的示例点云...")

    # 使用列表推导式和现代 Python 语法
    points: List[List[float]] = []
    colors: List[List[float]] = []

    # 定义面和颜色的映射 - Python 3.12.7 支持更好的字典类型注解
    face_colors: dict[int, tuple[List[float], str]] = {
        0: ([1, 0, 0], "红色 (前面)"),
        1: ([0, 1, 0], "绿色 (后面)"),
        2: ([0, 0, 1], "蓝色 (右面)"),
        3: ([1, 1, 0], "黄色 (左面)"),
        4: ([1, 0, 1], "品红色 (上面)"),
        5: ([0, 1, 1], "青色 (下面)")
    }

    # 生成立方体表面的点
    for i in range(num_points):
        face_id = np.random.randint(0, 6)
        color, face_name = face_colors[face_id]

        # 使用 match-case 语句 (Python 3.10+，在 3.12.7 中更稳定)
        match face_id:
            case 0:  # 前面 (z=1)
                x, y = np.random.uniform(-1, 1, 2).tolist()
                point = [x, y, 1.0]
            case 1:  # 后面 (z=-1)
                x, y = np.random.uniform(-1, 1, 2).tolist()
                point = [x, y, -1.0]
            case 2:  # 右面 (x=1)
                y, z = np.random.uniform(-1, 1, 2).tolist()
                point = [1.0, y, z]
            case 3:  # 左面 (x=-1)
                y, z = np.random.uniform(-1, 1, 2).tolist()
                point = [-1.0, y, z]
            case 4:  # 上面 (y=1)
                x, z = np.random.uniform(-1, 1, 2).tolist()
                point = [x, 1.0, z]
            case 5:  # 下面 (y=-1)
                x, z = np.random.uniform(-1, 1, 2).tolist()
                point = [x, -1.0, z]
            case _:  # 默认情况
                raise ValueError(f"无效的面 ID: {face_id}")

        points.append(point)
        colors.append(color)

    # 创建 Open3D 点云对象
    point_cloud = o3d.geometry.PointCloud()
    point_cloud.points = o3d.utility.Vector3dVector(points)
    point_cloud.colors = o3d.utility.Vector3dVector(colors)

    logger.info(f"创建了包含 {len(points)} 个点的点云")
    return point_cloud


def basic_point_cloud_operations(pcd: PointCloud) -> PointCloud:
    """
    演示基本的点云处理操作

    Args:
        pcd: 输入点云

    Returns:
        处理后的点云（此函数不修改点云，仅展示信息）
    """
    logger.info("=== 基本点云信息 ===")

    # 使用 f-string 和类型注解
    num_points: int = len(pcd.points)
    has_colors: bool = pcd.has_colors()
    has_normals: bool = pcd.has_normals()

    print(f"点云包含 {num_points:,} 个点")  # Python 3.12.7: 改进的数字格式化
    print(f"点云是否有颜色: {has_colors}")
    print(f"点云是否有法向量: {has_normals}")

    # 计算点云的边界框
    bbox = pcd.get_axis_aligned_bounding_box()
    center = pcd.get_center()

    print(f"点云边界框: min={bbox.min_bound}, max={bbox.max_bound}")
    print(f"点云中心: {center}")

    return pcd


def downsample_point_cloud(pcd: PointCloud, voxel_size: float = 0.1) -> PointCloud:
    """
    对点云进行下采样处理

    Args:
        pcd: 输入点云
        voxel_size: 体素大小

    Returns:
        下采样后的点云

    Raises:
        ValueError: 当体素大小无效时
    """
    if voxel_size <= 0:
        raise ValueError(f"体素大小必须大于 0，当前值: {voxel_size}")

    logger.info("=== 点云下采样 ===")
    original_count = len(pcd.points)

    # 使用体素下采样
    downsampled_pcd = pcd.voxel_down_sample(voxel_size)
    new_count = len(downsampled_pcd.points)

    reduction_rate = (1 - new_count / original_count) * 100

    print(f"下采样前点数: {original_count:,}")
    print(f"下采样后点数: {new_count:,}")
    print(f"体素大小: {voxel_size}")
    print(f"点数减少: {reduction_rate:.1f}%")

    return downsampled_pcd


def remove_outliers(pcd: PointCloud, nb_neighbors: int = 20, std_ratio: float = 2.0) -> PointCloud:
    """
    去除点云中的异常点

    Args:
        pcd: 输入点云
        nb_neighbors: 考虑的邻近点数量
        std_ratio: 标准差倍数阈值

    Returns:
        去除异常点后的点云
    """
    logger.info("=== 异常点去除 ===")
    original_count = len(pcd.points)

    try:
        # 使用统计方法去除异常点
        cl, ind = pcd.remove_statistical_outlier(
            nb_neighbors=nb_neighbors,
            std_ratio=std_ratio
        )
        inlier_cloud = pcd.select_by_index(ind)

        new_count = len(inlier_cloud.points)
        removed_count = original_count - new_count

        print(f"去除异常点前: {original_count:,} 个点")
        print(f"去除异常点后: {new_count:,} 个点")
        print(f"移除了 {removed_count:,} 个异常点 ({removed_count / original_count * 100:.1f}%)")

        return inlier_cloud

    except Exception as e:
        logger.error(f"异常点去除失败: {e}")
        return pcd  # 返回原始点云


def estimate_normals(pcd: PointCloud, radius: float = 0.1, max_nn: int = 30) -> PointCloud:
    """
    估计点云的法向量

    Args:
        pcd: 输入点云
        radius: 搜索半径
        max_nn: 最大近邻点数

    Returns:
        带有法向量的点云
    """
    logger.info("=== 法向量估计 ===")

    # 估计法向量
    pcd.estimate_normals(
        search_param=o3d.geometry.KDTreeSearchParamHybrid(
            radius=radius, max_nn=max_nn
        )
    )

    # Python 3.12.7: 更好的字符串插值
    normal_count = len(pcd.normals) if pcd.has_normals() else 0
    print(f"法向量估计完成，生成了 {normal_count:,} 个法向量")
    print(f"搜索参数: 半径={radius}, 最大近邻={max_nn}")

    return pcd


def visualize_point_cloud(
        pcd: PointCloud,
        window_name: str = "Open3D 点云可视化",
        point_size: float = 2.0,
        background_color: Tuple[float, float, float] = (0.1, 0.1, 0.1)
) -> None:
    """
    可视化点云，包含交互功能说明

    Args:
        pcd: 要可视化的点云
        window_name: 窗口名称
        point_size: 点的显示大小
        background_color: 背景颜色 (R, G, B)
    """
    logger.info(f"启动可视化窗口: {window_name}")

    # 交互操作说明
    controls = [
        "鼠标左键拖拽: 旋转视角",
        "鼠标右键拖拽: 平移视角",
        "鼠标滚轮: 缩放",
        "按 'H' 键: 显示帮助",
        "按 'Q' 键或关闭窗口: 退出"
    ]

    print("\n🎮 交互操作说明:")
    for i, control in enumerate(controls, 1):
        print(f"  {i}. {control}")

    try:
        # 创建可视化窗口
        vis = o3d.visualization.Visualizer()
        vis.create_window(window_name=window_name, width=800, height=600)

        # 添加点云到场景
        vis.add_geometry(pcd)

        # 设置渲染选项
        render_option = vis.get_render_option()
        render_option.point_size = point_size
        render_option.background_color = np.array(background_color)

        # 运行可视化器
        vis.run()
        vis.destroy_window()

    except Exception as e:
        logger.error(f"可视化失败: {e}")
        print("❌ 可视化窗口启动失败，可能的原因:")
        print("  - 缺少图形界面支持")
        print("  - Open3D GUI 组件未正确安装")
        print("  - 系统图形驱动问题")


def save_point_cloud(pcd: PointCloud, filename: str | Path = "sample_pointcloud.ply") -> bool:
    """
    保存点云到文件

    Args:
        pcd: 要保存的点云
        filename: 文件名或路径

    Returns:
        保存是否成功
    """
    logger.info("=== 保存点云 ===")

    # Python 3.12.7: 支持 str | Path 联合类型
    file_path = Path(filename)

    try:
        success = o3d.io.write_point_cloud(str(file_path), pcd)

        if success:
            file_size = file_path.stat().st_size if file_path.exists() else 0
            print(f"✅ 点云已保存到: {file_path.absolute()}")
            print(f"📁 文件大小: {file_size:,} bytes")
        else:
            print("❌ 保存失败！")

        return success

    except Exception as e:
        logger.error(f"保存点云时出错: {e}")
        return False


def main() -> None:
    """
    主函数：执行完整的点云处理流程
    使用 Python 3.12.7 的异常处理和现代特性
    """
    print("🚀 Open3D 点云处理示例 - Python 3.12.7 优化版本")
    print("=" * 60)

    # Python 3.12.7: 更好的异常处理
    try:
        # 初始化处理器
        processor = PointCloudProcessor(random_seed=42)

        # 1. 创建示例点云
        print("\n📊 步骤 1: 创建示例点云")
        original_pcd = create_sample_point_cloud(num_points=1000)

        # 2. 显示基本信息
        print("\n📊 步骤 2: 分析点云基本信息")
        basic_point_cloud_operations(original_pcd)

        # 3. 可视化原始点云
        print("\n📊 步骤 3: 显示原始点云")
        visualize_point_cloud(
            copy.deepcopy(original_pcd),
            "原始点云 - Python 3.12.7",
            point_size=3.0
        )

        # 4. 点云下采样
        print("\n📊 步骤 4: 点云下采样")
        downsampled_pcd = downsample_point_cloud(original_pcd, voxel_size=0.05)

        # 5. 去除异常点
        print("\n📊 步骤 5: 去除异常点")
        clean_pcd = remove_outliers(downsampled_pcd, nb_neighbors=20, std_ratio=2.0)

        # 6. 估计法向量
        print("\n📊 步骤 6: 估计法向量")
        final_pcd = estimate_normals(clean_pcd, radius=0.1, max_nn=30)

        # 7. 可视化处理后的点云
        print("\n📊 步骤 7: 显示处理后的点云")
        visualize_point_cloud(
            final_pcd,
            "处理后的点云（带法向量）- Python 3.12.7",
            point_size=2.5,
            background_color=(0.05, 0.05, 0.15)
        )

        # 8. 保存结果
        print("\n📊 步骤 8: 保存处理结果")
        save_point_cloud(final_pcd, "processed_pointcloud_py312.ply")

        # 总结
        print("\n✅ 示例运行完成！ (Python 3.12.7 特性版本)")
        print("\n🎓 你已经学会了:")

        features = [
            "创建和加载点云",
            "基本点云信息查看",
            "点云下采样优化",
            "统计方法异常点去除",
            "法向量估计算法",
            "交互式 3D 可视化",
            "点云文件保存",
            "Python 3.12.7 类型注解",
            "现代异常处理机制",
            "面向对象编程实践"
        ]

        for i, feature in enumerate(features, 1):
            print(f"  ✓ {i:2d}. {feature}")

        print(f"\n🐍 Python 3.12.7 特性使用:")
        py312_features = [
            "类型注解和联合类型 (str | Path)",
            "match-case 模式匹配语句",
            "改进的 f-string 格式化",
            "现代异常处理机制",
            "pathlib.Path 对象操作",
            "日志配置和结构化输出"
        ]

        for feature in py312_features:
            print(f"  🆕 {feature}")

    except KeyboardInterrupt:
        print("\n⚠️  用户中断程序执行")
    except ImportError as e:
        print(f"❌ 导入错误: {e}")
        print("💡 请确保已安装所需依赖:")
        print("   pip install open3d>=0.18.0 numpy")
    except Exception as e:
        logger.exception(f"程序执行出错: {e}")
        print(f"❌ 运行出错: {e}")
        print("💡 故障排除建议:")
        print("  1. 检查 Python 版本是否为 3.12.7+")
        print("  2. 检查 Open3D 是否正确安装")
        print("  3. 检查系统图形界面支持")
        print("  4. 查看详细错误日志")


if __name__ == "__main__":
    main()